<!DOCTYPE html>
<html lang="zh-CN">
<head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
    <title>
        
            Go nil详解
        
    </title>
    <link rel="icon" href="/img/favicon.png"/>
    
<link rel="stylesheet" href="/css/style.css">

    
<link rel="stylesheet" href="/css/font-awesome.min.css">

    <!-- 
<link rel="stylesheet" href="/css/gitment.css">
 -->
    
<link rel="stylesheet" href="/css/hljs.min.css">

    
<script src="/js/hljs.min.js"></script>
  
    <script>
        var _hmt = _hmt || [];
        (function() {
          var hm = document.createElement("script");
          hm.src = "https://hm.baidu.com/hm.js?8ad9e7d37411eee585b4bd7194806394";
          var s = document.getElementsByTagName("script")[0]; 
          s.parentNode.insertBefore(hm, s);
        })();
    </script>  
    <!-- 
<script src="/js/gitment.browser.js"></script>
   -->
    <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
    <script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<meta name="generator" content="Hexo 5.3.0"><link rel="alternate" href="/atom.xml" title="Nick" type="application/atom+xml">
</head>
<body>
    <header class="header" id="header">
    <h1>
        <a class="title" href="/">
            Nick
        </a>
    </h1>
    <h2>
        <a class="motto">
            天地间的怪事莫过于侦察别人的一些和自己绝不相干的事了
        </a>
    </h2>
    <nav class="navbar">
        <ul class="menu">
            
            
                <li class="menu-item">
                    <a href="/" class="menu-item-link">
                        首页
                    </a>
                </li>
            
                
            
                <li class="menu-item">
                    <a href="/archives" class="menu-item-link">
                        归档
                    </a>
                </li>
            
                
            
                <li class="menu-item">
                    <a target="_blank" rel="noopener" href="https://github.com/StoneQI" class="menu-item-link">
                        Github
                    </a>
                </li>
            
                
            
                <li class="menu-item">
                    <a href="/atom.xml" class="menu-item-link">
                        RSS
                    </a>
                </li>
            
                
            
                
            
                <li class="menu-item">
                    <a href="/category-archive" class="menu-item-link">
                        目录
                    </a>
                </li>
            
                
                
                <li class="menu-item">
                    <a class="menu-item-link search">
                        Search                   
                        <i class="fa fa-long-arrow-right search-icon" aria-hidden="true"></i>
                    </a>
                        <input placeholder="Search..." class="search-input" style="display:none;border:none!important;" onkeydown="onEnter(event)" onkeypress="onEnter(event)"></input>
                </li>
                
        </ul>
    </nav>
</header>
    <main class="main">
        <article class="post">
            <h1>
                <a class="title" href="/2021/03/04/go-nil-type-md/"> 
                    Go nil详解 
                </a>
            </h1>
            <div class="meta">
                <a class="date"> 
                    <i class="fa fa-calendar" aria-hidden="true"></i>                    
                    2021-03-04   
                </a>
                
                <a class="category">
                    <i class="fa fa-th" aria-hidden="true"></i>  
                </a>
               
                <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/go/">go</a></li></ul>
                
                <a class="tag">
                    <i class="fa fa-tags" aria-hidden="true"></i>  
                </a>
                
                    <ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/tags/go/" rel="tag">go</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/nil/" rel="tag">nil</a></li></ul>
            </div>
<div class="toc">
  <ol class="toc-list"><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#Go-%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%BB%98%E8%AE%A4%E5%80%BC"><span class="toc-list-text">Go 类型的默认值</span></a><ol class="toc-list-child"><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B%EF%BC%88basic-type%EF%BC%89"><span class="toc-list-text">基本类型（basic type）</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#%E7%BB%84%E5%90%88%E7%B1%BB%E5%9E%8B%EF%BC%88composite-type%EF%BC%89"><span class="toc-list-text">组合类型（composite type）</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#%E4%B8%8D%E5%90%8C%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%BB%98%E8%AE%A4%E5%80%BC"><span class="toc-list-text">不同类型的默认值</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#%E4%B8%8D%E5%90%8C%E7%B1%BB%E5%9E%8B%E5%8F%98%E9%87%8F%E7%9A%84%E5%A3%B0%E6%98%8E%E4%B8%8E%E5%88%9D%E5%A7%8B%E5%8C%96"><span class="toc-list-text">不同类型变量的声明与初始化</span></a></li></ol></li><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#%E7%BB%84%E5%90%88%E7%B1%BB%E5%9E%8Bnil%E5%80%BC"><span class="toc-list-text">组合类型nil值</span></a><ol class="toc-list-child"><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-pointer"><span class="toc-list-text">nil pointer</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-slice"><span class="toc-list-text">nil slice</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-map"><span class="toc-list-text">nil map</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-channel"><span class="toc-list-text">nil channel</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-func"><span class="toc-list-text">nil func</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-interface"><span class="toc-list-text">nil interface</span></a><ol class="toc-list-child"><li class="toc-list-item toc-list-level-4"><a class="toc-list-link" href="#%E4%B8%8D%E8%A6%81%E8%BF%94%E5%9B%9E%E5%85%B7%E4%BD%93%E7%9A%84%E9%94%99%E8%AF%AF%E7%B1%BB%E5%9E%8B%EF%BC%8C%E8%80%8C%E5%BA%94%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9Enil"><span class="toc-list-text">不要返回具体的错误类型，而应直接返回nil</span></a></li></ol></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil%E7%9A%84%E6%9C%89%E6%95%88%E5%88%A9%E7%94%A8"><span class="toc-list-text">nil的有效利用</span></a></li></ol></li><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#nil%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B8%B8%E8%A7%81%E7%94%A8%E6%B3%95"><span class="toc-list-text">nil的一些常见用法</span></a><ol class="toc-list-child"><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-pointer%E7%94%A8%E6%B3%95"><span class="toc-list-text">nil pointer用法</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-slices%E7%94%A8%E6%B3%95"><span class="toc-list-text">nil slices用法</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-map%E7%94%A8%E6%B3%95"><span class="toc-list-text">nil map用法</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-channel%E7%94%A8%E6%B3%95"><span class="toc-list-text">nil channel用法</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-func%E7%94%A8%E6%B3%95"><span class="toc-list-text">nil func用法</span></a></li><li class="toc-list-item toc-list-level-3"><a class="toc-list-link" href="#nil-interface%E7%94%A8%E6%B3%95"><span class="toc-list-text">nil interface用法</span></a></li></ol></li><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#%E5%8F%82%E8%80%83%E9%98%85%E8%AF%BB"><span class="toc-list-text">参考阅读</span></a></li></ol>
</div>
            <div class="content">
                <h2 id="Go-类型的默认值"><a href="#Go-类型的默认值" class="headerlink" title="Go 类型的默认值"></a>Go 类型的默认值</h2><h3 id="基本类型（basic-type）"><a href="#基本类型（basic-type）" class="headerlink" title="基本类型（basic type）"></a>基本类型（basic type）</h3><ul>
<li>内置字符串类型：<code>string</code>.</li>
<li>内置布尔类型：<code>bool</code>.</li>
<li>内置数值类型：<ul>
<li><code>int8</code>、<code>uint8</code>（<code>byte</code>）、<code>int16</code>、<code>uint16</code>、<code>int32</code>（<code>rune</code>）、<code>uint32</code>、<code>int64</code>、<code>uint64</code>、<code>int</code>、<code>uint</code>、<code>uintptr</code>。</li>
<li><code>float32</code>、<code>float64</code>。</li>
<li><code>complex64</code>、<code>complex128</code>。</li>
</ul>
</li>
</ul>
<p>注意，<code>byte</code>是<code>uint8</code>的一个内置别名，<code>rune</code>是<code>int32</code>的一个内置别名。 </p>
<a id="more"></a>
<h3 id="组合类型（composite-type）"><a href="#组合类型（composite-type）" class="headerlink" title="组合类型（composite type）"></a>组合类型（composite type）</h3><p>Go支持下列组合类型：</p>
<ul>
<li>指针类型- 类C指针</li>
<li>结构体类型 - 类C结构体</li>
<li>函数类型 - 函数类型在Go中是一种一等公民类别</li>
<li>容器类型，包括:<ul>
<li>数组类型 - 定长容器类型</li>
<li>切片类型 - 动态长度和容量容器类型</li>
<li>映射类型（<code>map</code>）- 也常称为字典类型。在标准编译器中映射是使用哈希表实现的。</li>
</ul>
</li>
<li>通道类型- 通道用来同步并发的协程</li>
<li>接口类型- 接口在反射和多态中发挥着重要角色</li>
</ul>
<h3 id="不同类型的默认值"><a href="#不同类型的默认值" class="headerlink" title="不同类型的默认值"></a>不同类型的默认值</h3><table>
<thead>
<tr>
<th>类型</th>
<th>默认值</th>
</tr>
</thead>
<tbody><tr>
<td><code>bool</code></td>
<td><code>false</code></td>
</tr>
<tr>
<td><code>numbers</code></td>
<td><code>0</code></td>
</tr>
<tr>
<td><code>string</code></td>
<td><code>&quot;&quot;</code></td>
</tr>
<tr>
<td><code>pointer</code></td>
<td><code>nil</code></td>
</tr>
<tr>
<td><code>slice</code></td>
<td><code>nil</code></td>
</tr>
<tr>
<td><code>map</code></td>
<td><code>nil</code></td>
</tr>
<tr>
<td><code>channel</code></td>
<td><code>nil</code></td>
</tr>
<tr>
<td><code>function</code></td>
<td><code>nil</code></td>
</tr>
<tr>
<td><code>interface</code></td>
<td><code>nil</code></td>
</tr>
</tbody></table>
<p>值得注意的是 <code>struct</code>类型和数组类型零值不是<code>nil</code>。<code>Struct</code> 是各字段值为对应类型的零值，数组类型是各元素类型的零值。且不能将<code>struct</code>类型和数组类型和与nil`进行等值判断，语法校验不通过。</p>
<h3 id="不同类型变量的声明与初始化"><a href="#不同类型变量的声明与初始化" class="headerlink" title="不同类型变量的声明与初始化"></a>不同类型变量的声明与初始化</h3><p>基本类型声明时就会初始化为默认零值。组合类型则分为声明和初始化两阶段。因此只需要讨论组合类型。统一的理解是，只声明未初始化的组合类型变量都是<code>nil</code>。不过其中还有一些区别，下面一一讲解。</p>
<pre><code class="gedcom">// 声明
var a *int
var b *string
var c []int
var d map[string]string
var e chan int
var f func()
// 初始化
a = make([]int,2)

// 声明+初始化

c := make([]int,2)
d := make(map[string]string)
e := make(chan int)


c := []int&#123;1,2,3,4&#125;
d := map[string]string&#123;&quot;aaa&quot;:&quot;bbb&quot;&#125;

c := &amp;[]int&#123;1,2,3,4&#125;
d := &amp;map[string]string&#123;&quot;aaa&quot;:&quot;bbb&quot;&#125;
</code></pre>
<h2 id="组合类型nil值"><a href="#组合类型nil值" class="headerlink" title="组合类型nil值"></a>组合类型<code>nil</code>值</h2><h3 id="nil-pointer"><a href="#nil-pointer" class="headerlink" title="nil pointer"></a>nil pointer</h3><pre><code class="go">var w *int  //对应汇编 MOVQ    $0, &quot;&quot;.w+48(SP)
// 此时未初始化。 w为nil
if w == nil &#123;
    fmt.Println(&quot;w is nil&quot;)
&#125;
</code></pre>
<p>从汇编中可以看到 nil pointer 其实就是指向内存为0的指针。</p>
<p>对指针的操作有两种</p>
<ol>
<li>取值  <code>*w</code>, 对nil 指针取值会 panic。</li>
<li>调用 nil 指针对应的方法，，可以正常调用</li>
</ol>
<h3 id="nil-slice"><a href="#nil-slice" class="headerlink" title="nil slice"></a><code>nil slice</code></h3><p><code>slice</code> 分为两部分，一部分是<code>slice</code>本身的结构如下图，其中<code>ptr</code>指向具体元素。</p>
<p><code>make([]byte, 0)</code></p>
<p><img src="https://gitee.com/stoneqi/blogimage/raw/master/img/slice-struct.png"></p>
<p> <code>make([]byte, 5)</code></p>
<p><img src="https://gitee.com/stoneqi/blogimage/raw/master/img/slice-1.png"></p>
<p>一般都会区分两个概念，<code>nil slice</code> 和 <code>empty slice</code></p>
<p><code>nil slice</code> ，其中<code>ptr</code>声明未初始化，为<code>nil</code>。 和<code>nil</code>比较为<code>true</code></p>
<p><code>empty slice</code>，其中<code>ptr</code>声明并初始化，但是指向空间未分配。和<code>nil</code>比较为<code>false</code></p>
<pre><code class="go">var w []int
// 汇编代码
// 设置 ptr 为0
// MOVQ    $0, &quot;&quot;.w+112(SP)
// XORPS   X0, X0
// 设置 len 和 cap 为0
// MOVUPS  X0, &quot;&quot;.w+120(SP)

w == nil 为 true

w1 := make([]int, 0)
// 汇编代码，功能同上
// MOVQ    AX, &quot;&quot;.w1+88(SP)
// XORPS   X0, X0
// MOVUPS  X0, &quot;&quot;.w1+96(SP)


w1 == nil 为 false
</code></pre>
<p>从汇编代码能清晰的看到，<code>nil slice</code>和<code>empty slice</code>都初始化了slice结构，只是ptr的值不同。所以 声明一个<code>nil slice</code>后 可以直接使用</p>
<p><code>nil slice</code>的操作，和<code>empty slice</code>相同：</p>
<p> 可以进行<code> len</code>、<code>cap</code>、 <code>for range</code>、<code>append</code>。注意<code>slice</code>没有删除元素的方法，只能通过在此<code>slice</code>上新建<code>slice</code>来完成</p>
<pre><code class="go">// nil slices
var s []int
len(s)  // 0
cap(s)  // 0
for range s  // iterates zero times
s[i]  // panic: index out of range

append(s,1) // no eror
</code></pre>
<h3 id="nil-map"><a href="#nil-map" class="headerlink" title="nil map"></a><code>nil map</code></h3><p>和上述一样，也有两个概念<code>nil map</code>和<code>empty map</code></p>
<ul>
<li>map声明后初始化前，此时为<code>nil map</code></li>
<li>map声明后不能进行赋值，只有初始化后才能进行赋值操作，初始化后为<code>empty map</code></li>
</ul>
<pre><code class="go">var w map[int]string   // nil map
// 汇编
// MOVQ    $0, &quot;&quot;.w+64(SP)
w == nil 为 true

w1 := map[int]string&#123;&#125; // empty map
// 汇编代码太长，省略
w1 == nil 为 false
</code></pre>
<p>从汇编中可以发现，<code>nil map</code>仅仅声明了一个指针，并置为nil，而<code>empty map</code>则是声明了对应的数据结构。</p>
<p>因此 <code>nil map</code>在进行赋值操作前，必须初始化，这是与slice的不同点。</p>
<p><code>nil map</code>和<code>empty map</code>可进行的操作：</p>
<ul>
<li><code>nil map</code>可进行查找(查找任意值会返回数据类型的默认值)、删除、len和range操作，并不会报错 。但是不能进行赋值操作，必须初始化。</li>
<li><code>empty map</code>, 可进行map所有操作。</li>
<li>只声明一个map类型变量时，为<code>nil map</code></li>
<li>此时为<strong>只读map</strong>，无法进行写操作，否则会触发panic</li>
<li>map 构建分两步，<ul>
<li>map声明后初始化前，可进行查找、删除、len和range操作，并不会报错  ，此时为nil map</li>
<li>map声明后不能进行赋值，只有初始化后才能进行赋值操作，初始化后为empty map</li>
</ul>
</li>
<li><code>nil map</code>和<code>empty map</code>区别：<ul>
<li><code>nil map</code>：只声明未初始化，此时为只读map，不能写入操作，示例：<code>var m map[t]v</code></li>
<li><code>empty map</code>：空map，已初始化，可写入，示例：<code>m := map[t]v&#123;&#125;</code>或<code>m := make(map[string]string, 0)</code></li>
</ul>
</li>
</ul>
<pre><code class="go">// nil maps
var m map[t]u // nil map
m2 := map[t]u&#123;&#125; // empty map
len(m)  // 0
for range m // iterates zero times
v, ok := m[i] // zero(u), false
m[i] = x // panic: assignment to entry in nil map
</code></pre>
<h3 id="nil-channel"><a href="#nil-channel" class="headerlink" title="nil channel"></a><code>nil channel</code></h3><pre><code class="go">var w chan int       // nil chan
// 汇编代码
// MOVQ    $0, &quot;&quot;.w+56(SP)

w1 := make(chan int) 
// 汇编代码
// LEAQ    type.chan int(SB), AX
// MOVQ    AX, (SP)
// MOVQ    $0, 8(SP)
// PCDATA  $1, $1
// CALL    runtime.makechan(SB)
// MOVQ    16(SP), AX
// MOVQ    AX, &quot;&quot;.w1+48(SP)
</code></pre>
<p>从汇编可以看到 <code>channel</code> 也需要声明和初始化。<code>nil chan</code> 仅仅只是创建了一个为<code>nil</code>指针，初始化才会创建具体的结构体。</p>
<p><code>nil chan</code> 的操作:</p>
<ol>
<li>读 写 都会无限阻塞</li>
<li>close 会发生<code>panic</code></li>
</ol>
<pre><code class="go">// nil channels
var c chan t 
&lt;- c      // blocks forever
c &lt;- x    // blocks forever
close(c)  // panic: close of nil channel
</code></pre>
<h3 id="nil-func"><a href="#nil-func" class="headerlink" title="nil func"></a><code>nil func</code></h3><p><code>map</code>、<code>channel</code>、<code>function</code>的本质都是指向具体实现的指针，而对应类型的nil则是不指向任何地址。因此不做具体介绍</p>
<h3 id="nil-interface"><a href="#nil-interface" class="headerlink" title="nil interface"></a><code>nil interface</code></h3><p>interface底层由两部分组成：类型、值(type, value)，当二者均为nil时，此时interface才为nil。</p>
<pre><code class="go">var w io.Reader 此时为(nil,nil)
// 汇编
// XORPS   X0, X0
// MOVUPS  X0, &quot;&quot;.w+40(SP)
w == nil  // true 

var a *strings.Reader 此时为(*strings.Reader,nil)
w = a
// 汇编
MOVQ    $0, &quot;&quot;..autotmp_2+32(SP)
LEAQ    go.itab.*strings.Reader,io.Reader(SB), AX
MOVQ    AX, &quot;&quot;.w+40(SP)  // _type 为 *strings.Reader
MOVQ    $0, &quot;&quot;.w+48(SP)  // datac 为  nil


w = strings.NewReader(&quot;1234&quot;)  此时为(*strings.Reader, 对应数据地址)
// 汇编
LEAQ    go.string.&quot;1234&quot;(SB), AX
MOVQ    AX, (SP)
MOVQ    $4, 8(SP)
PCDATA  $1, $0
CALL    strings.NewReader(SB)
MOVQ    16(SP), AX    //strings.NewReader(SB) 返回的地址 放入 AX
MOVQ    AX, &quot;&quot;..autotmp_1+32(SP)
LEAQ    go.itab.*strings.Reader,io.Reader(SB), CX
MOVQ    CX, &quot;&quot;.w+40(SP) // _type 为 *strings.Reader
MOVQ    AX, &quot;&quot;.w+48(SP) // datac 为 strings.NewReader(SB) 返回的地址

var s Write // Write 此时 s 为nil，为一个纯接口

var p *Person           // nil of type *Person
var s Write = p  // 赋值后，s 带有了p的类型信息，但是 p 为nil，所以值为nil


</code></pre>
<p>有可得，当interface 为 nil时，变量为一个nil的指针；当不为nil值时，会实例化为下面一种结构体。</p>
<pre><code class="go">type eface struct &#123; // 16 字节 不包含任何方法的接口
    _type *_type
    data  unsafe.Pointer
&#125;
</code></pre>
<pre><code class="go">type iface struct &#123; // 16 字节 包含方法的接口
    tab  *itab
    data unsafe.Pointer
&#125;
</code></pre>
<h4 id="不要返回具体的错误类型，而应直接返回nil"><a href="#不要返回具体的错误类型，而应直接返回nil" class="headerlink" title="不要返回具体的错误类型，而应直接返回nil"></a>不要返回具体的错误类型，而应直接返回<code>nil</code></h4><p>下面展示返回类型为interface时的差异：</p>
<p>错误示例：</p>
<pre><code class="go">func do() error &#123;   // error(*doError, nil)
  var err *doError
  return err  // nil of type *doError
&#125;

func main() &#123;
  err := do()             // error(*doError, nil)
  fmt.Println(err == nil) // false
&#125;
</code></pre>
<p>正确示例：</p>
<pre><code class="go">func do() *doError &#123;   // nil of type *doError
  return nil
&#125;

func main() &#123;
  err := do()             // nil of type *doError
  fmt.Println(err == nil) // true
&#125;
</code></pre>
<p>再看下面这段代码，虽然<code>do()</code>返回nil，但<code>wrapDo()</code>返回依然是接口，也就是<code>类型为*doError，值为nil</code>的接口，此时拿到的返回值并不等于nil。</p>
<pre><code class="go">func do() *doError &#123;  // nil of type *doError
  return nil
&#125;

func wrapDo() error &#123; // error (*doError, nil)
  return do()       // nil of type *doError
&#125;

func main() &#123;
  err := wrapDo()   // error  (*doError, nil)
  fmt.Println(err == nil) // false
&#125;
</code></pre>
<h3 id="nil的有效利用"><a href="#nil的有效利用" class="headerlink" title="nil的有效利用"></a><code>nil</code>的有效利用</h3><ul>
<li><code>nil</code>类型接收者是可以正确调用方法的 <code>nil reveivers are userful</code></li>
<li><code>Keep nil (pointer) useful if possible, if not NewX()</code></li>
<li><code>Use nil slices, they&#39;re often fast enough</code></li>
<li><code>Use nil maps as read-only empty maps</code>，将nil map作为只读的空map（不能读nil map进行写入操作，否则会发生panic）</li>
<li><code>Use nil channel to disable a select case</code>，nil channel来阻塞selct/case语句</li>
<li><code>nil value can satisfy interface</code>，不同类型的nil值可满足interface，也就是可赋值给interface</li>
<li><code>Use nil interface to signal defaul</code>，使用nil的interface来标识使用缺省处理</li>
</ul>
<h2 id="nil的一些常见用法"><a href="#nil的一些常见用法" class="headerlink" title="nil的一些常见用法"></a>nil的一些常见用法</h2><h3 id="nil-pointer用法"><a href="#nil-pointer用法" class="headerlink" title="nil pointer用法"></a><code>nil pointer</code>用法</h3><ul>
<li><code>nil pointer</code>用来和nil比较确认是否为零值</li>
</ul>
<pre><code>var p *int
p == nil // true
*p // panic: runtime error: invalid memory address or nil pointer dereference
</code></pre>
<p>来看看，如何实现二叉树树的求和操作：</p>
<pre><code>type tree &#123;
  v int
  l *tree
  r *tree
&#125;

func (t *tree) Sum() int
</code></pre>
<p>第一种方案，有两个问题：</p>
<ul>
<li>代码冗余，重复的<code>if v != nil &#123;v.m()&#125;</code></li>
<li>当<code>t</code>为nil时，会发生<code>panic</code></li>
</ul>
<pre><code>var t *tree // nil of type *tree
sum := t.Sum() // panic
// 实现方案1
func (t *tree) Sum() int &#123;
  sum := t.v
  if t.l != nil &#123;
    sum += t.l.Sum()
  &#125;
  if t.r != nil &#123;
    sum += t.r.Sum()
  &#125;
  return sum
&#125;
</code></pre>
<p><code>nil</code>接收者，也可以正确调用方法，所以可以利用这一点，改造出方案2：</p>
<pre><code class="go">// 方案2：代码简洁、可断性提高很多
func (t *tree) Sum() int &#123;
  if t == nil &#123;
    return 0
  &#125;
  return v + t.l.Sum() + t.r.Sum()
&#125;
</code></pre>
<p>通过利用类型的nil，可以灵活实现是否为空的处理，已经便捷实现扩展函数：</p>
<pre><code class="go">func (t *tree) String() string &#123;
  if t == nil &#123;
    return &quot;&quot;
  &#125;
  return fmt.Sprint(t.l, t.v, t.r)
&#125;

func (t *tree) Find(v int) bool &#123;
  if t == nil &#123;
    return false
  &#125;
  return t.v go== v || t.l.Find(v) || t.r.Find(v)
&#125;
</code></pre>
<h3 id="nil-slices用法"><a href="#nil-slices用法" class="headerlink" title="nil slices用法"></a><code>nil slices</code>用法</h3><ul>
<li>不能对<code>nil slice</code>进行取值，否则会发生panic</li>
<li>可通过<code>append</code>函数对<code>nil slice</code>进行增加元素操作</li>
</ul>
<pre><code class="go">var s []int
len(s) // 0
cap(s) // 0
for range s // 执行0次
s[i] // panic: index out of range

for i := 0; i &lt;10; i++ &#123;
  fmt.Printf(&quot;len: %2d, cap: %2d\n&quot;, len(s), cap(s))
  s = append(s, i)
&#125;
</code></pre>
<h3 id="nil-map用法"><a href="#nil-map用法" class="headerlink" title="nil map用法"></a><code>nil map</code>用法</h3><ul>
<li><code>nil map</code>不能进行增加元素操作，因它还没有进行初始化</li>
<li>将<code>nil map</code>作为只读的空map</li>
</ul>
<pre><code class="go">var m map[t]u
len(m) // 0
for range m // 执行0次
v, ok := m[i] // v=zero(u), ok=false
m[i] = x      // panic: assignment to entry in nil map
// 有个nil map有用的例子
func NewGet(url string, headers map[string]string)(*http.Request, error)  &#123;
  req, err := http.NewRequest(http.MethodGet, url, nil)
  if err != nil &#123;
    return nil, err
  &#125;

  for k, v := range headers &#123;
    req.Header.Set(k, v)
  &#125;
  return req, nil
&#125;

// 调用时，传递headers
NewGet(
  &quot;http://google.com&quot;,
  map[string]string&#123;
    &quot;USER_AGENT&quot;:&quot;google/gopher&quot;,
  &#125;,  // go语言五十度灰，如果参数和)之间换行形式，参数尾部需追加,逗号
)

// 调用时，不传递headers，可以传递一个empty map空map
NewGet(
  &quot;http://google.com&quot;,
  map[string]string&#123;&#125;, // 传递空map，empty map
)

// 调用时，不传递headers，可以传递一个nil map
NewGet(
  &quot;http://google.com&quot;,
  nil, // 传递nil map，也是合法的
)
</code></pre>
<h3 id="nil-channel用法"><a href="#nil-channel用法" class="headerlink" title="nil channel用法"></a><code>nil channel</code>用法</h3><ul>
<li>不能对<code>nil channel</code>进行<code>close()</code>操作，发触发<code>panic: close of nil channel</code></li>
<li>不能对channel进行多次<code>close</code>，会触发 <code>panic: close of closed channel</code></li>
<li>关闭channel，在select/case中将依然能获取到值，但nil channel将阻塞读操作来失效select/case中逻辑</li>
</ul>
<pre><code class="go">var c chan t // nil of type chan t
// nil channel操作时
&lt;- c // block forever，持续阻塞
c &lt;- x // block forever，持续阻塞
close(c) // panic: close of nil channel，关闭nil channel发生panic

// 对于已关闭的channel，将发生如下现象
v := &lt;- c  //closed channel是可以被消费者继续读取的，在读完了有意义的数据之后，将读到一堆空值。比如这里的int类型就是0。
v, ok := &lt;-c  // zero(t), false 不会阻塞，返回零值和False
c &lt;-x // panic: send to close channel
close(x) // panic: close of closed channel，备注原文中错误
</code></pre>
<p>现在假设要实现一个合并函数，实现从两个通道中获取数据，然后写入out输出通道：</p>
<pre><code class="go">
func merge(out chan&lt;- int, a,b &lt;-chan int)  &#123;
  for &#123;
    select &#123;
      case v:= &lt;-a: // 当a/b通道关闭时，这里将持续获取到0
        out &lt;-v
      case v:= &lt;-b
        out &lt;-v
    &#125;
  &#125;

&#125;

</code></pre>
<p>改造代码后如下：</p>
<pre><code class="go">func merge(out chan&lt;- int, a,b &lt;-chan int)  &#123;
  var aClosed, bClosed bool

  for !aClosed || !bClosed &#123;
    select &#123;
    case v,ok := &lt;-a: // 此时通道关闭后，就不会再进行获取了
      if !ok &#123;
        aClosed = true
        fmt.Println(&quot;a is closed&quot;)
        continue
      &#125;
      out &lt;-v
    case v,ok := &lt;-b:
      if !ok &#123;
        bClosed = true
        fmt.Println(&quot;b is closed&quot;)
        continue
      &#125;
      out &lt;-v
    &#125;
  &#125;
  close(out) // 需要在不使用后进行close操作
&#125;
</code></pre>
<p>终于搞定了，提交代码，转交给测试吧！你会发现“a is closed” 和 “b is closed”可能会执行多次，为什么？因为外部逻辑如果关闭了a/b，此时还能够读取。此时上述代码中会有空转的逻辑，运行下，看看打印输出：</p>
<pre><code>a is closed
a is closed
a is closed
a is closed
a is closed // 很多次无用的空转逻辑
b is closed // 最后一次，a/b都未空时才结束循环
</code></pre>
<p>可能看到这里已经有些蒙了，但要明白，无论是否关闭一个chanel，都可以从中读取到值，而如果不需要对channel取值操作了，那么可以将其改为nil，这样会永远阻塞读，防止再发生读操作；同时应在入口增加非nil判断。</p>
<pre><code>func merge(out chan&lt;- int, a, b &lt;-chan int) &#123;
    for a != nil || b != nil &#123;
        select &#123;
        case v, ok := &lt;-a: // 此时通道关闭后，a == nil，在读取会永远阻塞
            if !ok &#123;
                a = nil
                fmt.Println(&quot;a is closed&quot;)
                continue
            &#125;
            out &lt;- v
        case v, ok := &lt;-b:
            if !ok &#123;
                b = nil
                fmt.Println(&quot;b is closed&quot;)
                continue
            &#125;
            out &lt;- v
        &#125;
    &#125;
    close(out) // 需要再不使用后进行close操作
&#125;
</code></pre>
<h3 id="nil-func用法"><a href="#nil-func用法" class="headerlink" title="nil func用法"></a><code>nil func</code>用法</h3><ul>
<li>go中函数是一等公民</li>
<li>函数可以作为struct结构体的字段，缺省值则为nil</li>
</ul>
<pre><code>type Foo struct &#123;
  f func() error // f is type of func() error
&#125;

// 常见用法，传输函数为nil，增加缺省处理
func NewServer(logger func(string, ...interface&#123;&#125;)) &#123;
  if logger == nil &#123;
    logger = log.Printf
  &#125;
  logger.Printf(&quot;initializng %s&quot;, os.Getenv(&quot;hostname&quot;))
&#125;
</code></pre>
<h3 id="nil-interface用法"><a href="#nil-interface用法" class="headerlink" title="nil interface用法"></a><code>nil interface</code>用法</h3><ul>
<li>将<code>nil interface</code>作为一种信号来使用</li>
<li>nil指针，不等于nil接口</li>
</ul>
<pre><code class="go">if err != nil &#123;
  ...
&#125;
type Summer interface &#123;
  Sum() int
&#125;

var t *tree // nil of type *tree
var s Summer = t // nil指针，可以是合法的interface类型的值
// 此时，对接接口类型变量s而言，其类型为*tree，值为nil，也就是说(*tree, nil)行的interface

fmt.Println(t==nil, s.Sum()) // true, 0

type ints []int
func (i ints) Sum() int &#123;
  s := 0
  for _, v := range i&#123;
    s += v
  &#125;
  return s
&#125;

var i ints
var s Summer = i // summer 为([]int,i)
fmt.Println(s==nil, s.Sum()) // false, 0
// 通过判断接口为nil，来给定缺省值
func doSum(s Summer) int &#123;
  if s == nil &#123;
    return 0
  &#125;
  return s.Sum()
&#125;

var t *tree
doSum(t) // interface的类型和值分别为：(*tree, nil)

var i ints
doSum(i) // (ints, nil)

doSum(nil) // (nil, nil)

http.HandleFunc(&quot;localhost:8080&quot;, nil) // 传递nil，则使用缺省处理
</code></pre>
<h2 id="参考阅读"><a href="#参考阅读" class="headerlink" title="参考阅读"></a>参考阅读</h2><ul>
<li><a target="_blank" rel="noopener" href="https://fivezh.github.io/2020/03/09/golang-nil/">理解Golang中的 nil | 小武的博客 (fivezh.github.io)</a></li>
</ul>

            </div>
          
           
            <div class="copyright">
                <div class="name">
                    <a>本文作者:</a>
                    <a>Nick Yang</a>
                </div>
                <div class="link">
                    <a>本文链接:</a>
                    <a class="permalink" href="https://stoneqi.github.io/2021/03/04/go-nil-type-md/">https://stoneqi.github.io/2021/03/04/go-nil-type-md/</a>
                </div>
                <div class="license">
                    <a>版权声明:</a>
                    <a>本博客所有文章除特别声明外，均采用许可协议 CC BY-NC-SA 3.0 CN。转载请注明出处！</a>
                </div>
            </div>
            

          


</article>


<div class="tip">
<button class="tip-btn">
    打赏
</button>
<div class="tip-img">
<ul>
    
<li>
    <img src="/img/aipay.jpg"></img>
</li>

 
<li>
    <img src="/img/wechatpay.png"></img>
</li>

</ul>
</div>
</div>

<div class="more">

    <div class="prev">
   <a href="/2021/03/04/go-mpsc-md/">  一个介于wait-free 和 lock-free的高性能MPSC队列</a>
    </div>

<div></div>

    <div class="next">
    <a href="/2021/02/28/hello-world/"> hello world </a>
    </div>
    
</div>

<div class="bdsharebuttonbox">
<a href="#" class="bds_weixin fa fa-weixin" data-cmd="weixin" title="分享到微信" style="color:#1cbd8f">
</a>
<a href="#" class="bds_tsina fa fa-weibo" data-cmd="tsina" title="分享到新浪微博" style="color:#ff6363">
</a>
<a href="#" class="bds_twi fa fa-twitter" data-cmd="twi" title="分享到Twitter" style="color:#00A7EB">
</a>
<a href="#" class="bds_fbook fa fa-facebook" data-cmd="fbook" title="分享到Facebook" style="color:#00A7EB">
</a>
</div>
<script>
window._bd_share_config={
    "common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"1","bdSize":"16"},
    "share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='../../../../../static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];
</script>
 
<!-- <div id="comments"></div> -->
<div id="gitalk-container"></div>

<script>
  //   var gitment = new Gitment({
  //   id: "Thu Mar 04 2021 10:41:36 GMT+0800",
  //   owner: "StoneQi",
  //   repo: "stoneqi.github.io",
  //   oauth: {
  //     client_id:"d9f4e8907ec2566c2307",
  //     client_secret: "2390fe6084c9227566e82de1755e3c3de4a13592",
  //   }
  // })
  // gitment.render('comments')

  const gitalk = new Gitalk({
  clientID: "d9f4e8907ec2566c2307",
  clientSecret: "2390fe6084c9227566e82de1755e3c3de4a13592",
  repo: "stoneqi.github.io",      // The repository of store comments,
  owner: "StoneQi",
  admin: ["StoneQi"],
  language: "zh-CN",
  id: "Thu Mar 04 2021 10:41:36 GMT+0800",      // Ensure uniqueness and length less than 50
  distractionFreeMode: false  // Facebook-like distraction free mode
})

gitalk.render('gitalk-container')
</script>
    </main>
    <a class="not-found">not found!</a>
    <div class="search-items">
    </div>
    <a href="#header" id="top" style="display:none">
        <i class="fa fa-sort-asc fa-2x"></i>
    </a>
    <footer class="footer">
    <div class="footer-copyright">©️2017
    <a href="//github.com/Vevlins/toki" class="link" target="_blank">Toki</a>  by Vevlins
    </div>    
</footer>

    
<script src="/js/jquery.js"></script>

    
<script src="/js/toki.js"></script>
  
  <script>hljs.initHighlightingOnLoad();</script>
</body>
</html>